![]() |
![]() |
|
Die Methoden der Klasse »Stream«Die wichtigsten Methoden aller Stream-Klassen dürften Read, Write und Seek sein, die an dieser Stelle allgemein beschrieben werden sollen. Einem schreibenden Stream müssen Sie die Daten übergeben, die in den Datenstrom geschrieben werden sollen. Die Write-Methode liest die Elemente byteweise ein und schreibt sie in den Strom. Der Empfänger des Datenstroms kann die Bytes mit Read entnehmen. Die Methode ermöglicht zudem die Festlegung der Position, ab welcher der Lese- bzw. Schreibvorgang beginnen soll. Beachten Sie auch, dass Write keinen Rückgabewert hat, während Read einen Integer liefert, dem Sie die Anzahl der gelesenen Bytes entnehmen können, die in einen Puffer geschrieben worden sind. Der Rückgabewert ist 0, wenn das Ende des Streams erreicht ist. Er kann aber auch kleiner sein als im dritten Parameter angegeben, wenn weniger Bytes im Stream eingelesen werden. Die Klasse Stream definiert zwei weitere, ähnliche Methoden, die jedoch jeweils nur immer ein Byte aus dem Datenstrom lesen oder in diesen hineinschreiben: ReadByte und WriteByte. Beide Methoden sind parameterlos und setzen den Positionszeiger innerhalb des Streams um eine (Byte-)Position weiter. Der Rückgabewert der ReadByte-Methode ist –1, wenn das Ende des Datenstroms erreicht ist. Um in einem Datenstrom ab einer vorgegebenen Position zu lesen oder zu schreiben, bietet sich die Seek-Methode an, bei deren Aufruf der Startpunkt für den Positionszeiger im Stream festgelegt wird, ab dem weitere E/A-Operationen beginnen sollen. Zudem müssen Sie auch die Verschiebung der Bytes ab einer festgelegten Ursprungsposition angeben. Letztere ist vom Typ SeekOrigin, in der die folgenden drei Konstanten definiert sind.
Mit SeekOrigin.Begin wird der Positionszeiger auf das erste Byte des Datenstroms gesetzt, mit SeekOrigin.Current behält er seine augenblickliche Position bei, und mit SeekOrigin.End wird er auf das Byte gesetzt, das als erstes den Bytes des vollständigen Streams folgt. Ausgehend von origin wird durch Addition von offset die gewünschte Startposition ermittelt. Ein Stream, der einmal geöffnet worden ist und Daten in den Puffer geschrieben hat, sollte ordnungsgemäß mit Close geschlossen werden. Sie haben jetzt einige Methoden im Schnelldurchlauf kennen gelernt. Alle erwähnten wollen wir zum Schluss in übersichtlicher tabellarischer Form zusammenfassen.
12.4.2 Die von »Stream« abgeleiteten Klassen im Überblick
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Stream-Typ | Beschreibung |
| BufferedStream | Die Klasse BufferedStream wird benutzt, um Daten eines anderen E/A-Datenstroms zu puffern. Ein Puffer ist ein Block von Bytes im Arbeitsspeicher des Rechners, der dazu benutzt wird, den Datenstrom zu cachen, um damit die Anzahl der Aufrufe an das Betriebssystem zu verringern. Dadurch lässt sich insgesamt die Effizienz verbessern. Diese Klasse wird immer im Zusammenhang mit anderen Klassen eingesetzt. |
| CryptoStream | Daten, die nicht in ihrem Originalzustand in einen Strom geschrieben werden sollen, lassen sich mit der Klasse CryptoStream verschlüsseln. CryptoStream wird immer zusammen mit einem anderen Stream kombiniert. |
| FileStream | Diese Klasse wird dazu benutzt, um Daten in Dateien des Dateisystems zu schreiben. Eine Netzwerkverbindung kann ebenfalls das Ziel dieses Datenstroms sein. |
| GZipStream | Mit den Methoden dieser Klasse können Sie Byteströme komprimieren und dekomprimieren. |
| MemoryStream | Meistens sind Dateien oder Netzwerkverbindungen das Ziel der Datenströme. Es kann jedoch auch sinnvoll sein, Daten bewusst temporär in den Hauptspeicher zu schreiben und sie später von dort wieder zu lesen. Viele Anwendungen arbeiten nach dem Prinzip, Daten in eine temporäre Datei zu speichern. Ein MemoryStream kann temporäre Dateien ersetzen und trägt damit zur Steigerung der Leistungsfähigkeit einer Anwendung bei, da das Schreiben und Lesen in den Hauptspeicher um ein Vielfaches schneller ist als das Schreiben auf die Festplatte. |
| NetworkStream | Ein Datenfluss basierend auf der Klasse NetworkStream sendet die Daten basierend auf Sockets. Das Besondere an diesem Datenstrom ist, dass er nur die Fähigkeit hat, Daten vollständig in den Strom zu schreiben oder aus diesem zu lesen. Der Zugriff auf beliebige Daten innerhalb des Stroms ist nicht möglich. |
Die Klasse FileStream ist die universelle und damit in vielen Anwendungsfällen am geeignetsten erscheinende Klasse. Sie hat die Fähigkeit, sowohl byteweise aus einer Datei zu lesen als auch byteweise in eine Datei zu schreiben. Außerdem kann ein Positionszeiger auf eine beliebige Position innerhalb des Streams gesetzt werden. Ein FileStream puffert die Daten, um die Ausführungsgeschwindigkeit zu erhöhen. Die Größe des Puffers beträgt standardmäßig 8 kByte.
Die FileStream-Klasse bietet eine Reihe von Konstruktoren an, um dem Objekt bestimmte Verhaltensweisen und Eigenschaften mit auf den Lebensweg zu geben:
| Public Sub New (String, FileMode) |
| Public Sub New (String, FileMode, FileAccess) |
| Public Sub New (String, FileMode, FileAccess, FileShare) |
| Public Sub New (String, FileMode, FileAccess, FileShare, Integer) |
| Public Sub New (String, FileMode, FileAccess, FileShare, _ |
| Integer, Boolean) |
Sie können ein FileStream-Objekt erzeugen, indem Sie im ersten Parameter eine Pfadangabe als Zeichenfolge übergeben. Der Parameter FileMode beschreibt, wie das Betriebssystem die Datei öffnen soll (FileMode.Append, FileMode.Create, FileMode.CreateNew .), FileAccess hingegen, wie auf die Datei zugegriffen werden darf (FileAccess.Read, FileAccess.Write oder FileAccess.ReadWrite). Sie haben diese Typen bereits im Abschnitt zur Klasse File kennen gelernt (siehe auch die Tabellen 12.2 und 12.3). Der Parameter vom Typ FileShare gibt an, ob ein gemeinsamer Zugriff auf die Datei möglich ist oder nicht (siehe auch Tabelle 12.4).
Der Puffer, in den ein FileStream die Daten zur Steigerung der Leistungsfähigkeit schreibt, ist standardmäßig 8 kByte groß. Mit dem Parameter vom Typ int können Sie die Größe des Puffers bei der Instanziierung beeinflussen. Mit dem letzten Parameter vom Typ Boolean kann noch angegeben werden, ob das Objekt asynchrone Zugriffe unterstützen soll. In Abschnitt 12.7 wird anhand eines Beispiels gezeigt, wie asynchrone Operationen implementiert werden.
Das folgende Codefragment demonstriert, wie mit einem FileStream-Objekt Daten in eine Datei geschrieben werden.
| Sub Main() |
| Dim arr() As Byte = {10, 20, 30, 40, 50, 60, 70, 80, 90, 100} |
| Dim path As String = "C:\Testfile.txt" |
| Dim fs As FileStream = New FileStream(path, FileMode.Create) |
| fs.Write(arr, 0, arr.Length) |
| End Sub |
Zunächst wird ein Byte-Array deklariert und mit insgesamt zehn Zahlen initialisiert. In der zweiten Anweisung wird der Dateiname, in den das Array geschrieben werden soll, festgelegt.
Bei der Instanziierung des FileStream-Objekts werden dem Konstruktor im ersten Argument der Pfad, auf dem der Stream operieren soll, und die Datei bekannt gegeben. Die Fähigkeiten dieses Streams beschreibt das zweite Argument: Die Konstante FileMode.Create teilt dem Konstruktor mit, dass das FileStream-Objekt eine neue Datei erzeugen kann oder, falls im angegebenen Pfad bereits eine gleichnamige Datei existiert, diese überschreiben soll. Mit
| fs.Write(arr, 0, arr.Length) |
wird der Inhalt des Arrays arr dem Stream-Objekt übergeben. Die Syntax der Methode Write der Klasse FileStream lautet wie folgt:
| Public Sub Write(array As Byte(),offset As Integer,count As Integer) |
Dabei haben die drei Parameter die folgende Bedeutung:
| Parameter | Beschreibung |
| array | Ein Byte-Array, in das die übergebenen Daten gelesen werden |
| offset | Die Indexposition im Array, bei dem die Leseoperation beginnen soll |
| count | Die Anzahl der zu lesenden Bytes |
Der Schreibvorgang des Beispiels startet mit dem ersten Array-Element. Das sagt der zweite Parameter der Write-Methode aus. Die Anzahl der zu schreibenden Bytes bestimmt der dritte Parameter – in unserem Beispiel werden alle Array-Elemente dem Datenstrom zugeführt.
Wir wollen uns nun auch vom Erfolg unserer Bemühungen überzeugen und die Datei auswerten. Dazu wird der Programmcode des Beispiels wie folgt ergänzt:
| Sub Main() |
| ... |
| Dim arrRead(9) As Byte |
| fs.Read(arrRead, 0, 10) |
| Dim i As Integer |
| For i = 0 To arr.Length – 1 |
| Console.WriteLine(arrRead(i)) |
| Next |
| fs.Close() |
| End Sub |
Wir deklarieren ein weiteres Array (arrRead), in das wir das Ergebnis der Leseoperation hineinschreiben. Da uns bekannt ist, wie viele Byte-Elemente sich in unserer Datei befinden (wie unfair), können wir die Array-Grenze schon im Voraus festlegen.
Nun kommt es zum Aufruf der Read-Methode. Zuerst wollen wir uns wieder die Syntax dieser Methode anschauen:
| Public Overrides Function Read(array As Byte(), _ |
| offset As Integer, count As Integer) As Intger |
Die Parameter sind denen der Write-Methode sehr ähnlich. Das FileStream-Objekt, auf das die Read-Methode aufgerufen wird, repräsentiert eine bestimmte Datei. Diese wurde bereits über dem Konstruktor bekannt gegeben. Aus der Datei werden die Daten in das Array eingelesen, das durch den Parameter array beschrieben wird. Der erste der Schreiboperation zur Verfügung stehende Array-Index wird im Parameter offset angegeben, die Anzahl der aus dem FileStream zu lesenden Bytes im dritten Parameter count.
Wir wollen den ersten Byte-Wert aus dem Datenstrom in das mit 0 indizierte Element des Arrays arrRead schreiben und geben das im zweiten Parameter bekannt. Die Gesamtanzahl der zu lesenden Bytes teilen wir dem dritten Parameter mit. In einer Schleife wird danach das eingelesene Array durchlaufen und an der Konsole ausgegeben.
Starten Sie nun die Laufzeit des Programms, wird die Enttäuschung groß sein und Sie an den eigenen Programmierfähigkeiten zweifeln lassen! Denn bedauerlicherweise werden nicht die Zahlenwerte, die wir in die Datei geschrieben haben, ausgegeben, sondern nur Nullen. Haben wir etwas falsch gemacht, und, wenn ja, wie ist das zu erklären?
Die Schreib- bzw. Leseposition in einem Datenstrom wird durch einen Positionszeiger beschrieben. Schließlich muss der Strom wissen, auf welchem Byte die folgende Operation ausgeführt werden soll. Bei der Instanziierung eines Stream-Objekts verweist der Zeiger zunächst auf das erste Byte im Stream. Mit dem Aufruf der Write-Methode wird ein Wert daher genau in diese Position geschrieben. Anschließend wird der Positionszeiger auf die folgende Byteposition verschoben.
Dieser Vorgang wiederholt sich bei jedem Schreibvorgang, von denen es in unserem Beispiel zehn gibt, nämlich für jedes zu schreibende Array-Element einen. Am Ende, wenn wir unsere zehn Bytes in den Strom geschrieben haben, verweist der Positionszeiger auf das folgende, nun elfte Byte im Stream (siehe Abbildung 12.3). Genau das verursacht nun ein Problem. Wir rufen auf das FileStream-Objekt die Read-Methode auf und lesen ab der Position, die aktuell durch den Datenzeiger beschrieben wird. Das ist aber die elfte Stelle im Datenstrom – und nicht die erste, wie wir es eigentlich erwartet haben bzw. wie es hätte sein sollen.

Hier klicken, um das Bild zu Vergrößern
Abbildung 12.3 Der Positionszeiger in einem »Stream«-Objekt
Kommen wir nun zur Lösung. FileStream beerbt die Klasse Stream und hat daher auch eine Seek-Methode, mit der wir den Positionszeiger beliebig verbiegen können:
| Public Overrides Function Seek(offset As Long, _ |
| origin As SeekOrigin) As Long |
Wir überlegen uns, wohin wir den Ursprung des Positionszeigers verlegen wollen – natürlich an den Anfang des Datenstroms. Also muss der zweite Parameter der Seek-Methode auf
| origin = SeekOrigin.Begin |
festgelegt werden (siehe dazu auch Tabelle 12.13). Nun geben wir im ersten Argument den tatsächlichen und endgültigen Startpunkt des Positionszeigers bezogen auf den im zweiten Parameter definierten Ursprung an. Er lautet 0, denn schließlich wollen wir den Zeiger auf die erste Position des Datenstroms setzen.
| ... |
| fs.Write(arr, 0, arr.Length) |
| ... |
| fs.Seek(0, SeekOrigin.Begin) |
| fs.Read(arrRead, 0, 10) |
| ... |
| Anmerkung |
|
Natürlich wäre es auch möglich, zunächst das FileStream-Objekt zu schließen und es danach neu zu instanziieren. Damit hätten wir wieder einen Positionszeiger, der auf das erste Byte im Stream zeigt. |
Wenn wir nach der Ergänzung mit Seek das Programm noch einmal starten, wird das Ergebnis wie erwartet ausgegeben.
Zum Schluss wollen wir noch einmal den gesamten Code zusammenfassen.
| ' ---------------------------------------------------------- |
| ' Beispiel: ...\Kapitel 12\FileStreamDemo |
| ' ---------------------------------------------------------- |
| Imports System.IO |
| Module Module1 |
| Sub Main() |
| Dim arr() As Byte = {10, 20, 30, 40, 50, 60, 70, 80, 90, 100} |
| Dim path As String = "C:\Testfile.txt" |
| Dim fs As FileStream = New FileStream(path, FileMode.Create) |
| fs.Write(arr, 0, arr.Length) |
| Dim arrRead(9) As Byte |
| ' Positionszeiger auf den Anfang des Streams setzen |
| fs.Seek(0, SeekOrigin.Begin) |
| ' Stream lesen |
| fs.Read(arrRead, 0, 10) |
| Dim i As Integer |
| For i = 0 To arr.Length – 1 |
| Console.WriteLine(arrRead(i)) |
| Next |
| Console.ReadLine() |
| ' FileStream schließen |
| fs.Close() |
| End Sub |
| End Module |
Wir müssen nicht zwangsläufig das komplette Byte-Array vom ersten bis zum letzten Element in die Datei schreiben. Mit
| fs.Write(arr, 2, arr.Length – 2) |
ist das erste zu schreibende Element dasjenige, das die Zahl 30 enthält (entsprechend dem Index 2). Wenn wir allerdings den dritten Parameter, der die Anzahl der zu lesenden Bytes angibt, nicht entsprechend anpassen, wird über das Ende des Arrays hinausgelesen, was zu der Exception ArgumentException führt.
In Abschnitt 12.3.1 haben Sie anhand eines Beispiels gesehen, wie eine Textdatei eingelesen werden kann. In dem Beispiel (auf der Buch-CD unter \Kapitel 12\TextdateiLesen zu finden) hatten wir dazu die Klasse StreamReader benutzt. Auch mit einem FileStream-Objekt kann problemlos auf eine Textdatei zugegriffen werden. Wir wollen uns das nachfolgend ansehen:
| ' ---------------------------------------------------------- |
| ' Beispiel: ...\Kapitel 12\FileStream-Textdatei |
| ' ---------------------------------------------------------- |
| Imports System.IO |
| Module Module1 |
| Sub Main() |
| ' Benutzereingabe anfordern |
| Console.Write("Geben Sie die zu öffnende Datei an: ") |
| Dim strFile As String = Console.ReadLine() |
| ' prüfen, ob die angegebene Datei existiert |
| If Not File.Exists(strFile) Then |
| Console.WriteLine("Die Datei {0} existiert nicht!", strFile) |
| Console.ReadLine() |
| Return |
| End If |
| ' Datei öffnen |
| Dim fs As FileStream = File.Open(strFile, FileMode.Open) |
| ' Byte-Array, in das die Daten aus dem Datenstrom ein- |
| ' gelesen werden |
| Dim puffer(fs.Length – 1) As Byte |
| ' die Zeichen aus der Datei lesen und in das Array |
| ' schreiben, der Lesevorgang beginnt mit dem ersten Zeichen |
| fs.Read(puffer, 0, fs.Length) |
| ' das Byte-Array elementweise einlesen und jedes |
| ' Array-Element in Char konvertieren |
| Dim i As Integer |
| For i = 0 To fs.Length – 1 |
| Console.Write(Convert.ToChar(puffer(i))) |
| Next |
| Console.ReadLine() |
| End Sub |
| End Module |
Nach dem Start der Laufzeit wird der Benutzer dazu aufgefordert, den Pfad zu einer Textdatei anzugeben. Diesmal beschreiten wir allerdings einen anderen Weg und rufen den Konstruktor nicht direkt auf, sondern die Methode Open der Klasse File:
| Dim fs As FileStream = File.Open(strFile, FileMode.Open) |
Diese Anweisung funktioniert tadellos, weil der Rückgabewert der File.Open-Methode die Referenz auf eine FileStream-Instanz liefert. Gegen den Weg über einen FileStream-Konstruktor, der diese Möglichkeit auch bietet, ist zwar grundsätzlich nichts einzuwenden, wir wollen uns allerdings die gezeigte Alternative vor Augen führen.
Im folgenden Schritt wird das Byte-Array puffer deklariert und mit einer Kapazität initialisiert, die der Größe der Datei entspricht:
| Dim puffer(fs.Length – 1) As Byte |
Die Größe der Datei besorgen wir uns mit der Eigenschaft Length der Klasse FileStream, die uns die Größe des Datenstroms liefert. Daran schließt sich die Leseoperation mit Read an, die den Inhalt der Textdatei byteweise liest und in das Array puffer schreibt:
| fs.Read(puffer, 0, fs.Length) |
Ein FileStream-Objekt arbeitet grundsätzlich nur auf der Basis von Bytes, es weiß nichts von dem tatsächlichen Typ, der sich im Datenstrom verbirgt. Eine Textdatei enthält aber Zeichen, die als ANSI-Zeichen interpretiert erst den wirklichen Informationsgehalt liefern. Deshalb müssen wir jedes einzelne Byte des Streams in einen Char-Typ konvertieren. Das geschieht in einer Schleife, die alle Bytes des Arrays abgreift, konvertiert und danach an der Konsole ausgibt.
| For i = 0 To fs.Length – 1 |
| Console.Write(Convert.ToChar(puffer(i))) |
| Next |
Würden die Daten aus der Datei einem anderen Typ zugrunde liegen, beispielsweise Integer oder Single, müsste dieser Zieldatentyp angegeben werden. Wissen Sie nicht, welcher Typ in der Datei gespeichert ist, können Sie mit dem Inhalt praktisch nichts anfangen.
| << zurück |
|
||||||||||||||
|
||||||||||||||
|
||||||||||||||
|
||||||||||||||
Copyright © Galileo Press 2007
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken.
Ansonsten unterliegt das <openbook> denselben Bestimmungen, wie die
gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich
geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung,
Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.